package com.roadmap.client.auth;

import java.util.Date;
import java.util.List;
import java.util.Vector;

import org.apache.log4j.Logger;

import com.roadmap.biz.message.NoticeManager;
import com.roadmap.common.BaseConst;
import com.roadmap.common.BaseEntity;
import com.roadmap.common.BaseUtility;
import com.roadmap.common.RdcLogger;
import com.roadmap.common.RdcResourceBundle;
import com.roadmap.common.BaseEnum.ActionType;
import com.roadmap.entities.Aspirant;
import com.roadmap.entities.Registration;
import com.roadmap.exception.DuplicateEntityException;
import com.roadmap.exception.EntityNotFoundException;
import com.roadmap.exception.InvalidActionException;
import com.roadmap.exception.InvalidQueryException;
import com.roadmap.exception.InvalidValueException;
import com.roadmap.exception.NotRegisteredException;
import com.roadmap.exception.NullObjectException;
import com.roadmap.exception.ServerException;
import com.roadmap.repo.db.DatabaseUtility;
import com.roadmap.repo.db.IDatabaseUtility;
import com.roadmap.util.RdcEncryptor;
import com.roadmap.util.mail.MailDispatcher;

/** services for user registration */
public class RegisterServices implements IRegisterServices {
	private final static Logger logger = RdcLogger
			.getLogger(RegisterServices.class);
	private final IDatabaseUtility dbUtil = new DatabaseUtility();
	private final NoticeManager actRecorder;
	private static String registerUrl = null;

	public RegisterServices(String server) {
		actRecorder = new NoticeManager(null, dbUtil);
		
		if(registerUrl == null) {
			registerUrl = RdcResourceBundle.REG_MAIL_CONTENT.replace(BaseConst.PLACEHOLDER_SERVER_IN_MAIL, server);
		}
	}
	
	public RegisterServices() {
		actRecorder = new NoticeManager(null, dbUtil);
	}

	/**
	 * get validation codes for mail
	 * 
	 * @param urlcode:
	 *            it consists of two parts: registration Id (32 characters) and
	 *            random code (6 characters)
	 */
	public String getValidateCodes(String mail, String ip)
			throws NullObjectException, ServerException, InvalidValueException,
			InvalidActionException, DuplicateEntityException {

		Registration reg = getRegistration(mail, ip);
		String urlcode = reg.getId() + reg.getRandomCode();
		logger.debug("URL codes for validating mail was generated. " + urlcode);

		return urlcode;
	}

	/** examine if the mail address already exists in database */
	public Registration getRegFromDB(String mail) throws ServerException,
			NullObjectException, InvalidValueException, InvalidActionException {
		if (mail == null) {
			NullObjectException noe = new NullObjectException();
			logger.error("mail can't be NULL", noe);
			throw noe;
		}

		Registration reg = null;
		List list = dbUtil.getEntitiesByCriteria(Registration.class, "mail='"
				+ mail + "'");
		if (list != null && list.size() > 0) {
			reg = (Registration) list.get(0);
			logger.debug("got a registration from database: " + reg.getId());
		}
		return reg;
	}

	/**
	 * get a registration. if the mail is registered, get data from database, or
	 * else create a new and save it. when DuplicateRegisterException is
	 * triggered, it means the mail was already registered.
	 */
	private Registration getRegistration(String mail, String ip)
			throws ServerException, NullObjectException, InvalidValueException,
			InvalidActionException, DuplicateEntityException {
		if (mail == null || ip == null) {
			NullObjectException noe = new NullObjectException();
			logger.error("mail or IP can't be NULL", noe);
			throw noe;
		}

		Registration reg = getRegFromDB(mail);
		if (reg == null) {
			logger.debug("going to create a new registration");
			// the mail is not registered before, so generate a new record
			reg = new Registration();
		}
		
		if (reg.getStatus() == BaseConst.REGISTRATION_STATUS_NotPassed) {
			// if the mail registration already passed, no need to update it in
			// database, or else update it
			logger.debug("going to update the registration: " + reg.getId());
			// set IP and random code
			reg.setMail(mail);
			reg.setIpAddress(ip);
			reg.setRandomCode(BaseUtility.getRandomString());
			reg.setUpdatedTime(new Date());
			dbUtil.saveOrUpdateEntity(reg);
			
			// send a mail
			sendMail(mail, registerUrl + reg.getId()
					+ reg.getRandomCode());
		}
		else {
			DuplicateEntityException dre = new DuplicateEntityException();
			logger.error("the registration for the mail " + reg.getMail()
					+ " aleady passed.", dre);
			throw dre;
		}
		return reg;
	}

	/**
	 * after validation of mail passed, finish registration with user inputs
	 * 
	 * @throws NotRegisteredException
	 */
	public Aspirant finishRegistration(String urlCode, String realName,
			String password, char sex, Date birthday, String ipAddress)
			throws ServerException, NullObjectException, InvalidValueException,
			InvalidActionException, EntityNotFoundException,
			DuplicateEntityException, NotRegisteredException {
		if (urlCode == null || realName == null || password == null
				|| birthday == null || ipAddress == null) {
			NullObjectException noe = new NullObjectException();
			logger.error("registration object can't be NULL");
			throw noe;
		}
		if (sex != BaseConst.ASPIRANT_SEX_MALE
				&& sex != BaseConst.ASPIRANT_SEX_FEMALE) {
			InvalidValueException ive = new InvalidValueException();
			logger.error("user's sex is invalid");
			throw ive;
		}

		Registration reg = retrieveRegistrationByUrlcode(urlCode);
		Date date = new Date();
		// change the registration's status
		reg.setStatus(BaseConst.REGISTRATION_STATUS_Passed);
		reg.setUpdatedTime(date);
		reg.setIpAddress(ipAddress);

		// create a Aspirant instance
		Aspirant asp = new Aspirant();
		asp.setMail(reg.getMail());
		asp.setRealName(realName);
		asp.setBirthday(birthday);
		asp.setSex(sex);
		asp.setLoginPassword(RdcEncryptor.getInstace().encode(password));
		asp.setLoginIP(ipAddress);
		asp.setUpdatedTime(date);

		Vector<BaseEntity> entities = new Vector<BaseEntity>();
		entities.add(reg);
		entities.add(asp);
		dbUtil.saveOrUpdateEntities(entities);
		if (asp.getId() != null) {
			if (logger.isDebugEnabled()) {
				String msg = "successfully finished registration for the aspirant: ";
				logger.debug(msg + asp.getRealName());
			}
			actRecorder.setAspirantId(asp.getId());
			actRecorder.notifyForEntry(asp, ActionType.CREATE, null);
			return asp;
		}
		else {
			NotRegisteredException nre = new NotRegisteredException();
			logger.error("failed to finish registration for the aspirant: "
					+ asp.getRealName(), nre);
			throw nre;
		}
	}

	/**
	 * resolve validation code from url. the return string array consists of two
	 * strings
	 * 
	 * @param urlcode:
	 *            it consists of two parts: registration Id (32 characters) and
	 *            random code (6 characters)
	 * @return a array of String. only two strings in the array, the first one
	 *         is registration id and the other one is code
	 */
	private String[] resolveCodes(String urlcode) throws NullObjectException,
			InvalidValueException {
		if (urlcode == null) {
			NullObjectException noe = new NullObjectException();
			logger.error("registration object can't be NULL");
			throw noe;
		}

		urlcode = urlcode.trim();
		if (urlcode.length() != BaseConst.DATABASE_TABLE_ID_LENGTH
				+ BaseConst.RANDOM_STRING_LENGTH) {
			InvalidValueException ive = new InvalidValueException();
			logger.error("registration code is invalid");
			throw ive;
		}

		String[] codes = new String[2];
		codes[0] = urlcode.substring(0, BaseConst.DATABASE_TABLE_ID_LENGTH);
		codes[1] = urlcode.substring(BaseConst.DATABASE_TABLE_ID_LENGTH,
				urlcode.length());

		if (logger.isDebugEnabled()) {
			logger
					.debug("the registration id is: " + codes[0] + ","
							+ codes[1]);
		}
		return codes;
	}

	/** get registration record from database. */
	private Registration retrieveRegistrationByUrlcode(String urlCode)
			throws DuplicateEntityException, InvalidValueException,
			EntityNotFoundException, ServerException, InvalidQueryException {
		if (urlCode == null)
			return null;

		String[] codes = resolveCodes(urlCode);
		Registration reg = null;
		BaseEntity ent = dbUtil.getEntityById(Registration.class, codes[0]);
		// check the registration record
		if (ent == null) {
			/* if no record got, that indicates the url code is wrong */
			EntityNotFoundException enfe = new EntityNotFoundException();
			logger.error(codes[0] + "doesn't exist in database");
			throw enfe;
		}
		else {
			reg = (Registration) ent;
			if (reg.getStatus() == BaseConst.REGISTRATION_STATUS_Passed) {
				// this mail was already registered successfully
				DuplicateEntityException dre = new DuplicateEntityException();
				logger.error("the registration for the mail " + reg.getMail()
						+ " aleady passed.");
				throw dre;
			}
			else if (reg.getRandomCode().equalsIgnoreCase(codes[1]) == false) {
				// registration code is incorrect
				InvalidValueException ive = new InvalidValueException();
				logger.error("registration code is invalid");
				throw ive;
			}
		}

		return reg;
	}

	public void deleteRegistration(String mail) throws NullObjectException,
			ServerException, InvalidValueException, InvalidActionException {
		BaseEntity entity = getRegFromDB(mail);
		dbUtil.delete(entity);
	}

	/** send mail to validate the mail address which is registered by the user */
	private boolean sendMail(String mailAddr, String message) {
		return MailDispatcher.send(mailAddr,
				RdcResourceBundle.REG_MAIL_SUBJECT, message);
	}
}
